use std::borrow::Borrow;

use wither::{
    bson::{doc, oid::ObjectId, DateTime, Document},
    mongodb::options::{FindOneAndUpdateOptions, ReturnDocument},
    Model,
};

use crate::{
    errors::cons_error,
    mdb::ModelEx,
    mdb::{MClient, MDB},
    models::{
        article::{ArticleModel, ArticleSectionModel},
        user::SimpleUserModel,
        watch_record::{WatchRecordContent, WatchRecordModel},
    },
    utils::traits::UpdateModelData,
    AppError, AppResult, ArcConfig, Pagination, PaginationQuery,
};
#[allow(unused)]
#[derive(Debug, Clone)]
pub struct ArticleService {
    mdb: MDB,
    m_client: MClient,
    config: ArcConfig,
}

impl ArticleService {
    /// ## 构造文章服务
    pub fn new(config: ArcConfig, mdb: MDB, m_client: MClient) -> Self {
        Self {
            config,
            mdb,
            m_client,
        }
    }

    /// ## 搜索所有的板块
    pub async fn search_all_section<F: Into<Option<Document>>, Q: Borrow<PaginationQuery>>(
        &self,
        filter: F,
        query: Q,
    ) -> AppResult<Pagination<ArticleSectionModel>> {
        let mut filter: Document = filter.into().unwrap_or_default();
        filter.insert("is_enabled", true);

        ArticleSectionModel::find_and_pagination(self.mdb.clone(), filter, query.borrow()).await
    }
    /// ## 搜索所有的文章
    pub async fn search_all_article<
        S: Into<Option<ObjectId>>,
        F: Into<Option<Document>>,
        Q: Borrow<PaginationQuery>,
    >(
        &self,
        section_id: S,
        filter: F,
        query: Q,
    ) -> AppResult<Pagination<ArticleModel>> {
        let mut filter: Document = filter.into().unwrap_or_default();
        let section_id: Option<ObjectId> = section_id.into();
        if let Some(section_id) = section_id {
            filter.insert("section_id", section_id);
        }
        filter.insert("valid", true);
        ArticleModel::find_and_pagination(self.mdb.clone(), filter, query.borrow()).await
    }
    pub async fn find_one_article<I: Into<Option<ObjectId>>, F: Into<Option<Document>>>(
        &self,
        id: I,
        filter: F,
    ) -> AppResult<Option<ArticleModel>> {
        let mut filter: Document = filter.into().unwrap_or_default();
        let id: Option<ObjectId> = id.into();
        if let Some(id) = id {
            filter.insert("_id", id);
        }
        filter.insert("valid", true);

        Ok(ArticleModel::find_one(&*self.mdb, filter, None).await?)
    }

    pub async fn create_section<T: Into<CreateSectionSvrReq>>(
        &self,
        req: T,
    ) -> AppResult<ArticleSectionModel> {
        let req: CreateSectionSvrReq = req.into();
        let mut article_section = ArticleSectionModel::from(req);
        article_section.save(&*self.mdb, None).await?;
        Ok(article_section)
    }
    /// ## 删除一个板块
    /// 使用的是软删除
    pub async fn delete_section<T: Into<ObjectId>>(
        &self,
        section_id: T,
    ) -> AppResult<ArticleSectionModel> {
        let section_id: ObjectId = section_id.into();
        let options = FindOneAndUpdateOptions::builder()
            .return_document(ReturnDocument::After)
            .build();
        let res = ArticleSectionModel::find_one_and_update(
            &*self.mdb,
            doc! {"_id":section_id},
            doc! {"$set":{"valid":false}},
            options,
        )
        .await?
        .ok_or(AppError::ConsError(cons_error::MSG_MDB_NOT_FOUND))?;
        Ok(res)
    }

    /// ## 更新文章板块内容
    pub async fn update_section<T: Into<UpdateSectionSvrReq>>(
        &self,
        req: T,
    ) -> AppResult<ArticleSectionModel> {
        let req: UpdateSectionSvrReq = req.into();
        let options = FindOneAndUpdateOptions::builder()
            .return_document(ReturnDocument::After)
            .build();
        let mut update_doc = req.get_update_set_doc()?;
        update_doc.insert("updated_at", DateTime::now());
        let res = ArticleSectionModel::find_one_and_update(
            &*self.mdb,
            req.get_update_filter_doc()?,
            doc! {
                "$set":update_doc
            },
            options,
        )
        .await?
        .ok_or(AppError::ConsError(cons_error::MSG_MDB_NOT_FOUND))?;
        Ok(res)
    }

    /// ## 创建一篇文章
    pub async fn create_article<T: Into<CreateArticleSvrReq>>(
        &self,
        req: T,
    ) -> AppResult<ArticleModel> {
        let req: CreateArticleSvrReq = req.into();
        let mut article_model = ArticleModel::from(req);
        if let Some(section_id) = article_model.section_id {
            // 如果设置了板块id那么就需要确定板块是否存在
            ArticleSectionModel::find_by_id(&*self.mdb, section_id, None)
                .await?
                .ok_or(AppError::ConsError(cons_error::MSG_SECTION_IS_NOT_FOUND))?;
        }
        article_model.save(&*self.mdb, None).await?;
        Ok(article_model)
    }
    pub async fn update_article<T: Into<UpdateArticleSvrReq>>(
        &self,
        req: T,
    ) -> AppResult<ArticleModel> {
        let req: UpdateArticleSvrReq = req.into();
        if let Some(section_id) = req.section_id {
            // 同创建，如果设置了section_id需要检查是否有效
            ArticleSectionModel::find_by_id(&*self.mdb, section_id, None)
                .await?
                .ok_or(AppError::ConsError(cons_error::MSG_SECTION_IS_NOT_FOUND))?;
        }
        let filter = req.get_update_filter_doc()?;
        let mut set = req.get_update_set_doc()?;
        set.insert("updated_at", DateTime::now());
        let options = FindOneAndUpdateOptions::builder()
            .return_document(ReturnDocument::After)
            .build();
        let res = ArticleModel::find_one_and_update(
            &*self.mdb,
            filter,
            doc! {
                "$set":set
            },
            options,
        )
        .await?
        .ok_or(AppError::ConsError(cons_error::MSG_ARTICLE_IS_NOT_FOUND))?;
        Ok(res)
    }
    /// ## 删除文章
    pub async fn delete_article<T: Into<ObjectId>>(
        &self,
        article_id: T,
    ) -> AppResult<ArticleModel> {
        let article_id: ObjectId = article_id.into();
        let options = FindOneAndUpdateOptions::builder()
            .return_document(ReturnDocument::After)
            .build();
        let res = ArticleModel::find_one_and_update(
            &*self.mdb,
            doc! {"_id":article_id},
            doc! {
                "$set":{
                    "valid":false
                }
            },
            options,
        )
        .await?
        .ok_or(AppError::ConsError(cons_error::MSG_ARTICLE_IS_NOT_FOUND))?;
        Ok(res)
    }
    /// ## 恢复文章的删除状态
    pub async fn recover_article<T: Into<ObjectId>>(
        &self,
        article_id: T,
    ) -> AppResult<ArticleModel> {
        let article_id: ObjectId = article_id.into();
        let options = FindOneAndUpdateOptions::builder()
            .return_document(ReturnDocument::After)
            .build();
        let res = ArticleModel::find_one_and_update(
            &*self.mdb,
            doc! {"_id":article_id},
            doc! {
                "$set":{
                    "valid":true
                }
            },
            options,
        )
        .await?
        .ok_or(AppError::ConsError(cons_error::MSG_ARTICLE_IS_NOT_FOUND))?;
        Ok(res)
    }

    pub async fn article_detail<T: Into<ObjectId>>(
        &self,
        article_id: T,
    ) -> AppResult<ArticleModel> {
        let article_id: ObjectId = article_id.into();
        let res = ArticleModel::find_by_id(&*self.mdb, article_id, None)
            .await?
            .ok_or_else(|| AppError::not_found(cons_error::MSG_ARTICLE_IS_NOT_FOUND))?;
        Ok(res)
    }
    pub async fn section_detail<T: Into<ObjectId>>(
        &self,
        section_id: T,
    ) -> AppResult<ArticleSectionModel> {
        let section_id: ObjectId = section_id.into();
        let res = ArticleSectionModel::find_by_id(&*self.mdb, section_id, None)
            .await?
            .ok_or_else(|| AppError::not_found(cons_error::MSG_SECTION_IS_NOT_FOUND))?;
        Ok(res)
    }

    pub async fn watch_article_record<T: Into<ObjectId>, U: Into<SimpleUserModel>>(
        &self,
        article_id: T,
        user: U,
    ) -> AppResult<ArticleModel> {
        let article_id: ObjectId = article_id.into();
        let mut article = self.article_detail(article_id).await?;
        let model_data =
            WatchRecordModel::record_user_watch(user, WatchRecordContent::Article { article_id });
        let filter = model_data.get_filter_document()?;
        let insert_doc = model_data.document_from_instance()?;
        let coll = WatchRecordModel::collection(&*self.mdb);
        let options = FindOneAndUpdateOptions::builder()
            .upsert(true)
            .return_document(ReturnDocument::Before)
            .build();
        let res = coll
            .find_one_and_update(
                filter,
                doc! {
                    "$set":{},
                    "$setOnInsert":insert_doc
                },
                options,
            )
            .await?;
        if res.is_some() {
            return Err(AppError::forbidden(cons_error::MSG_ALREADY_WATCH));
        }
        article.watch_count += 1;
        article.save(&*self.mdb, None).await?;
        Ok(article)
    }

    pub async fn watch_section_record<T: Into<ObjectId>, U: Into<SimpleUserModel>>(
        &self,
        section_id: T,
        user: U,
    ) -> AppResult<ArticleSectionModel> {
        let section_id: ObjectId = section_id.into();
        let mut section = self.section_detail(section_id).await?;
        let model_data =
            WatchRecordModel::record_user_watch(user, WatchRecordContent::Section { section_id });
        let filter = model_data.get_filter_document()?;
        let insert_doc = model_data.document_from_instance()?;
        let coll = WatchRecordModel::collection(&*self.mdb);
        let options = FindOneAndUpdateOptions::builder()
            .upsert(true)
            .return_document(ReturnDocument::Before)
            .build();
        let res = coll
            .find_one_and_update(
                filter,
                doc! {
                    "$set":{},
                    "$setOnInsert":insert_doc
                },
                options,
            )
            .await?;

        if res.is_some() {
            return Err(AppError::forbidden(cons_error::MSG_ALREADY_WATCH));
        }
        section.daily_count += 1;
        section.save(&*self.mdb, None).await?;
        Ok(section)
    }
}
mod service_struct {
    use wither::bson::{oid::ObjectId, to_document, Document};

    use crate::{
        models::{
            article::{ArticleAuthor, ArticleModel, ArticleSectionModel},
            Assets,
        },
        utils::{strings::text_cut_all, traits::UpdateModelData},
        AppError,
    };

    #[derive(Debug, Clone)]
    pub struct CreateSectionSvrReq {
        pub name: String,
        pub description: Option<String>,
        pub color: String,
    }
    impl Default for CreateSectionSvrReq {
        fn default() -> Self {
            Self {
                name: "noname".to_string(),
                description: None,
                color: "#FFFFFF".to_string(),
            }
        }
    }
    impl From<CreateSectionSvrReq> for ArticleSectionModel {
        fn from(c: CreateSectionSvrReq) -> Self {
            Self {
                name: c.name,
                description: None,
                color: c.color,
                ..Default::default()
            }
        }
    }
    #[derive(Debug, Clone)]
    pub struct UpdateSectionSvrReq {
        pub id: ObjectId,
        pub name: Option<String>,
        pub description: Option<String>,
        pub color: Option<String>,
    }

    impl Default for UpdateSectionSvrReq {
        fn default() -> Self {
            Self {
                id: ObjectId::new(),
                name: None,
                description: None,
                color: None,
            }
        }
    }

    impl UpdateModelData for UpdateSectionSvrReq {
        type Error = AppError;
        fn get_update_set_doc(&self) -> Result<Document, Self::Error> {
            let mut doc = Document::new();
            if let Some(ref name) = self.name {
                doc.insert("name", name);
            }
            if let Some(ref description) = self.description {
                doc.insert("description", description);
            }
            if let Some(ref color) = self.color {
                doc.insert("color", color);
            }
            Ok(doc)
        }
        fn get_update_filter_doc(&self) -> Result<Document, Self::Error> {
            let mut doc = Document::new();
            doc.insert("_id", self.id);
            Ok(doc)
        }
    }

    #[derive(Debug, Clone)]
    pub struct CreateArticleSvrReq {
        pub section_id: Option<ObjectId>,
        pub title: String,
        pub cover_image: Option<Assets>,
        pub description: Option<String>,
        pub author: ArticleAuthor,
        pub content: Option<Document>,
    }
    impl From<CreateArticleSvrReq> for ArticleModel {
        fn from(req: CreateArticleSvrReq) -> Self {
            Self {
                section_id: req.section_id,
                title: req.title,
                cover_image: req.cover_image,
                description: req.description,
                author: req.author,
                content: req.content,
                ..Default::default()
            }
        }
    }

    #[derive(Debug, Clone)]
    pub struct UpdateArticleSvrReq {
        pub id: ObjectId,
        pub section_id: Option<ObjectId>,
        pub title: Option<String>,
        pub cover_image: Option<Assets>,
        pub description: Option<String>,
        pub content: Option<Document>,
    }
    impl UpdateModelData for UpdateArticleSvrReq {
        type Error = AppError;
        fn get_update_filter_doc(&self) -> Result<Document, Self::Error> {
            let mut doc = Document::new();
            doc.insert("_id", self.id);
            Ok(doc)
        }
        fn get_update_set_doc(&self) -> Result<Document, Self::Error> {
            let mut doc = Document::new();
            if let Some(ref section_id) = self.section_id {
                doc.insert("section_id", section_id);
            }
            if let Some(ref title) = self.title {
                doc.insert("title", title);
                doc.insert("title_word", text_cut_all(title));
            }
            if let Some(ref cover_image) = self.cover_image {
                doc.insert("cover_image", to_document(cover_image)?);
            }
            if let Some(ref description) = self.description {
                doc.insert("description", description);
                doc.insert("description_word", text_cut_all(description));
            }
            if let Some(ref content) = self.content {
                doc.insert("content", content);
            }
            Ok(doc)
        }
    }
}

pub use service_struct::*;
